package com.louis.springboot.demo.security;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.Assert;

/**
 * 身份验证提供者
 * @author Louis
 * @date Jun 29, 2019
 */

/**
 * 默认实现：DaoAuthenticationProvider
 * 授权方式提供者，判断授权有效性，用户有效性，在判断用户是否有效性，
 * 它依赖于UserDetailsService实例，开发人员可以自定义UserDetailsService的实现。
 * additionalAuthenticationChecks方法校验密码有效性
 * retrieveUser方法根据用户名获取用户
 * createSuccessAuthentication完成授权持久化
 */
public class JwtAuthenticationProvider extends DaoAuthenticationProvider {

    public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
        setUserDetailsService(userDetailsService);
        setPasswordEncoder(new BCryptPasswordEncoder());
    }

  //  @Override
   // public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    	// 可以在此处覆写整个登录认证逻辑
    //	return super.authenticate(authentication);
   // }
    @Override
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
            return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
        });
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.getUserCache().getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                user = this.retrieveUser(username,
                        (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            this.getPreAuthenticationChecks().check(user);
            this.additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.getPreAuthenticationChecks().check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }

        this.getPostAuthenticationChecks().check(user);
        if (!cacheWasUsed) {
            this.getUserCache().putUserInCache(user);
        }

        Object principalToReturn = user;
//        if (this.forcePrincipalAsString) {
//            principalToReturn = user.getUsername();
//        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

    ///检查用户名和密码是否匹配

    /**
     * Spring Security中。提交的用户名和密码，
     * 被封装成了UsernamePasswordAuthenticationToken，
     * 而根据用户名加载用户的任务则是交给了UserDetailsService，
     * UserDetailsService :获得已注册用户信息
     * 在DaoAuthenticationProvider中，对应的方法便是retrieveUser，
     * 返回一个UserDetails。
     * 然后再将UsernamePasswordAuthenticationToken和UserDetails密码的比对，
     * 这便是交给additionalAuthenticationChecks方法完成的，
     * 如果这个void方法没有抛异常，则认为比对成功。

     *
     * @param userDetails
     * @param authentication
     * @throws AuthenticationException
     */
    @Override
	protected void additionalAuthenticationChecks(UserDetails userDetails,
                                                  UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
    	// 可以在此处覆写密码验证逻辑
		super.additionalAuthenticationChecks(userDetails, authentication);
	}

}